/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package exclusionmutua;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.protocol.PipeAdvertisement;

/**
 *
 * @author Eduardo
 */
public class ExclusionMutua {

    /**La clase que permite el envio de mensajes de exlcusión mutua atravez de Jxta*/
    private ExclusionMutuaJxta emj;
    /**Define el documento al cual pertenece esta exlcusión mutua*/
    private String idDocumento;
    /**Identifica si este sitio es el propietario del archivo o no*/
    private boolean propietario;
    /**El nodo que es el propietario de esta Exclusión Muta*/
    private PipeAdvertisement pipePropietario;
    /**Define la cantidad de nodos que existe en el sistema distribuido**/
    public static final int N = 3;
    /**Array de enteros, que mantiene la sequencia mas grande recibida de un
     * sitio j, j = PeerID.
     */
    private Map<PipeAdvertisement, Integer> RN = new HashMap<PipeAdvertisement, Integer>(N);
    /**Es el id de este Peer**/
    private PipeAdvertisement thisPipeAdvertisment;
    /**Token, si no lo posee es null*/
    private Token token;
    /**Nodo dueño de este documento*/
    private final String nodoOrigen;

    /**
     * Constructor que crea una nueva exclusión mutua
     * 
     * @param idDocumento El objeto al cual esta asociado esta exlcusión mutua.
     * @param thisPipeAdvertisement El peer en el que se esta ejecutando.
     * @param propDescarga El pipe utilizado para la descarga una vez se tiene el token.
     * @param propSubida El pipe utilizado para la subida una vez se tiene el token.
     * @param nodoOrigen Nombre del nodo dueño de este documento.
     */
    public ExclusionMutua(String idDocumento,
            PipeAdvertisement thisPipeAdvertisement,
            ExclusionMutuaJxta emj,
            PipeAdvertisement propDescarga,
            PipeAdvertisement propSubida,
            String nodoOrigen) {
        this.idDocumento = idDocumento;
        this.thisPipeAdvertisment = thisPipeAdvertisement;
        this.emj = emj;
        if(propDescarga != null && propSubida != null){
            this.token = new Token(idDocumento, propDescarga, propSubida);
            this.propietario = true;
            this.pipePropietario = propDescarga;
        }else{
            token = null;
            propietario = false;
            this.pipePropietario = null;
        }
        this.nodoOrigen = nodoOrigen;
    }

    public String getNodoOrigen() {
        return nodoOrigen;
    }

    public PipeAdvertisement getPipePropietario() {
        return pipePropietario;
    }

    public void setPipePropietario(PipeAdvertisement pipePropietario) {
        this.pipePropietario = pipePropietario;
    }

    public String getIdDocumento() {
        return idDocumento;
    }

    public Token getToken() {
        return token;
    }
    /**Si se esta usando el token es true**/
    private boolean tokenEnUso = false;

    public boolean isTokenEnUso() {
        return tokenEnUso;
    }

    public void setTokenEnUso(boolean tokenEnUso) {
        this.tokenEnUso = tokenEnUso;
    }

    public void requestingCS(TokenListener listener,int numSec,String origen,String color) throws IOException {
        //1. Si el solicitante no tiene el token, entonces incrementa su sequenceNumber
        //y envia una peticion a todos los otros sitios
        if (token == null) {
            Integer sequenceNumber = RN.get(thisPipeAdvertisment);
            sequenceNumber++;
            RN.put(thisPipeAdvertisment, sequenceNumber);
            //Enviar solicitd de requerimiento de SC a todos los nodos de la red
            sendRequest(listener,numSec,origen,color);
        } else if (!isTokenEnUso()) {
            listener.gestionarToken(token);
            tokenEnUso = true;
        } else {
            System.err.println("Se solicitó el token, pero ya se tiene y esta en uso.");
        }
    }

    public void receiveRequest(PipeAdvertisement remotePipe, Integer sequenceNumber,String destino,int secuencia)
            throws IOException {

        System.out.println("Recibi mensaje de peticion de Sección Critica");
        int maxSeqNumb = Math.max(sequenceNumber, RN.get(remotePipe));
        RN.put(remotePipe, maxSeqNumb);
        if (token != null && !isTokenEnUso()) {
            //No siempre que se recibe la solicitud se tiene el token por lo tanto
            //tambien se tiene que verificar que se posea el token
            if (compare_RNSeqNumb_LNSeqNumb(remotePipe)) {
                enviarToken(remotePipe, token,destino,secuencia);
            }
        }else{
            System.out.println("No se tiene el token o se tiene y esta esta en uso actualizando RN");
        }


    }

    public void releaseCS() throws IOException {
        //1. Asignar RN[i] a LN[i]
        token.setLNSequenceNumber(thisPipeAdvertisment, RN.get(thisPipeAdvertisment));
        //2. Por cada sitio Sj cuyo id no este en la cola del token se anade a 
        //la cola del token si RNi[j] = LN[j] + 1
        Set<PipeAdvertisement> peers = RN.keySet();
        //Probar el metodo para utilizar la siguiente sentencia en vez de la anterior
        //Set<PeerID> peers = token.getNoEstanEnCola();
        for (PipeAdvertisement pipe : peers) {
            if (compare_RNSeqNumb_LNSeqNumb(pipe)) {
                token.addSolicitante(pipe);
            }
        }
        PipeAdvertisement nuevoSolicitante = token.removeSolicitante();
        if (nuevoSolicitante != null) {
            enviarToken(nuevoSolicitante, token,null,0);
        }
    }



    /**
     * Envia una petición de exclusion mutua a todos los nodos de la red Jxta.
     *
     */

    private void sendRequest(TokenListener listener,int numSec,String origen,String color) throws IOException {
        Integer sequenceNumber = RN.get(thisPipeAdvertisment);
        emj.enviarMensajeRequest(idDocumento, sequenceNumber, listener,numSec,origen,color);
    }

    /**
     * Metodo que envia el token a un nodo que hizo solicitud de entrar en la
     * sección critica.
     *
     * @param nuevoSolicitante Peer que solicita el token.
     * @param token El token que posse este peer y que sera enviado al nuevoSolicitante.
     */
    private void enviarToken(PipeAdvertisement nuevoSolicitante, Token token,String destino,int secuencia) throws IOException {
        try {
            emj.enviarToken(nuevoSolicitante, token, destino, secuencia);
            setTokenEnUso(false);
            this.token = null;
            Thread.sleep(4000);
            System.out.println("Se borró el token y se estableció el uso en false.");
        } catch (InterruptedException ex) {
            Logger.getLogger(ExclusionMutua.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Recibe el token despues de una petición.
     *
     * @param ReceivedToken el token recibido.
     */
    public void receiveToken(Token receivedToken) {
        if (token != null) {
            System.err.println("El token ya se había recibido");
            return;
        }
        token = receivedToken;
        setTokenEnUso(true);
        System.out.println("Se guardó el token correctamente.");
    }

    /**
     * Verifica que el vector RN[peer] de este nodo sea igual al vector del token
     * LN[peer] + 1. Es decir RN[peer] = LN[peer] + 1.
     * @param peer
     * @return
     */
    private boolean compare_RNSeqNumb_LNSeqNumb(PipeAdvertisement peer) {
        if (token == null) {
            throw new NullPointerException("Token nulo al intentar comprobar RN[peer] = LN[peer] + 1");
        }
        int RN_SeqNumb = RN.get(peer);
        int LN_SeqNumb = token.getLNSquenceNumber(peer);
        return RN_SeqNumb == (LN_SeqNumb + 1);
    }

    /**
     * Añade un nuevo peer al sistema de Exclusión Mutua.
     *
     * @param remotePipe Peer a añadir al sistema de Exclusión Mutua
     */
    public void addPeerRemoto(PipeAdvertisement remotePipe) {
        //Añade el pper al vector RN
        if (!RN.containsKey(remotePipe)) {
            RN.put(remotePipe, new Integer(0));
        }
        //Añade el peer al token (en caso de que sea el propietario del documento)
        if (token != null) {
            token.setLNSequenceNumber(remotePipe, new Integer(0));
        }
    }

    /**
     * Obtiene la referencia de todos los otros peers dentro de la red jxta. La
     * información la obtiene del vector RN quitandose el mismo de la lista.
     * @return Las referencias a los otros peers.
     */
    private Set<PipeAdvertisement> getOtherPeers() {
        Set<PipeAdvertisement> otherPeers = new HashSet<PipeAdvertisement>(RN.keySet());
        otherPeers.remove(thisPipeAdvertisment);
        return otherPeers;
    }

    /**
     * Informa si este nodo es el propietario del archivo de exclusión mutua.
     * @return true si es el propietario, false en caso contrario.
     */
    public boolean isPropietario() {
        return propietario;
    }


}
